# -*- coding: UTF-8 -*-
class MergeDict:
    """
    Une classe simple permettant de créer un dictionnaire "virtuel" formé en
    fait de plusieurs dictionnaires, que vous devez passer au constructeur.
    """
    def __init__(self, *dicts):
        self.dicts = dicts

    def __getitem__(self, key):
        for dict in self.dicts:
            try:
                return dict[key]
            except KeyError:
                pass
        raise KeyError

    def get(self, key, default):
        try:
            return self[key]
        except KeyError:
            return default

    def getlist(self, key):
        for dict in self.dicts:
            try:
                return dict.getlist(key)
            except KeyError:
                pass
        raise KeyError

    def items(self):
        item_list = []
        for dict in self.dicts:
            item_list.extend(dict.items())
        return item_list

    def has_key(self, key):
        for dict in self.dicts:
            if dict.has_key(key):
                return True
        return False

class MultiValueDictKeyError(KeyError):
    pass

class MultiValueDict:
    """
    Une classe décrivant un dictionnaire optimisé pour gérer les clés dont les valeurs
    sont des séquences.

    >>> d = MultiValueDict({'nom': ['Adrian', 'Simon'], 'poste': [u'Développeur']})
    >>> d['nom']
    'Simon'
    >>> d.getlist('nom')
    ['Adrian', 'Simon']
    >>> d.get('prenom', 'n\'existe pas')
    "n'existe pas"
    >>> d.setlist('prenom', ['Holovaty', 'Willison'])

    Cette classe a été créee pour résoudre l'irritant problème soulevé par la
    fonction cgi.parse_qs, qui renvoie un dictionnaire dont chaque valeur est une
    liste, et ce même si la plupart des formulaires web génèrent des paires
    nom/valeur simples.
    """
    def __init__(self, key_to_list_mapping=None):
        self.data = key_to_list_mapping or {}

    def __repr__(self):
        return repr(self.data)

    def __getitem__(self, key):
        "Renvoie la valeur contenue dans la séquence correspondant à la clé spécifiée; déclenche KeyError si la clé est introuvable."
        if self.data.has_key(key):
            try:
                return self.data[key][-1] # s'il y a plus d'un élément dans la séquence, on renvoie le dernier élément (indice -1)
            except IndexError:
                return []
        raise MultiValueDictKeyError, "La clé '%s' est introuvable dans l'objet MultiValueDict %s." % (key, self.data)

    def __setitem__(self, key, value):
        self.data[key] = [value]

    def __len__(self):
        return len(self.data)

    def get(self, key, default):
        "Renvoie la valeur par défaut spécifiée si la clé spécifiée n'existe pas."
        try:
            val = self[key]
        except (KeyError, IndexError):
            return default
        if val == []:
            return default
        return val

    def getlist(self, key):
        "Renvoie une liste vide si la clé spécifiée n'existe pas."
        try:
            return self.data[key]
        except KeyError:
            return []

    def setlist(self, key, list_):
        self.data[key] = list_

    def appendlist(self, key, item):
        "Ajoute un élément à la liste correspondant à la clé key."
        try:
            self.data[key].append(item)
        except KeyError:
            self.data[key] = [item]

    def has_key(self, key):
        return self.data.has_key(key)

    def items(self):
        # on ne renvoie pas simplement self.data.items(), car il faut que nous
        # utilisions self.__getitem__() afin d'avoir les valeurs sous forme de
        # *chaînes* et non de listes
        return [(key, self[key]) for key in self.data.keys()]

    def keys(self):
        return self.data.keys()

    def update(self, other_dict):
        if isinstance(other_dict, MultiValueDict):
            for key, value_list in other_dict.data.items():
                self.data.setdefault(key, []).extend(value_list)
        elif type(other_dict) == type({}):
            for key, value in other_dict.items():
                self.data.setdefault(key, []).append(value)
        else:
            raise ValueError, "La méthode MultiValueDict.update() accepte en argument soit un objet MultiValueDict soit un dictionnaire."

    def copy(self):
        "Renvoie une copie de cet objet."
        import copy
        cp = copy.deepcopy(self)
        return cp

class DotExpandedDict(dict):
    """
    Une classe permettant de construire un dictionnaire spécial; le constructeur
    accepte en argument un dictionnaire dont les clés peuvent contenir
    des points qui spécifient alors à leur tour des dictionnaires imbriqués. Cette
    description est assez confuse, mais l'exemple suivant sera sûrement beaucoup
    plus parlant :

    >>> d = DotExpandedDict({'personne.1.prenom': ['Simon'],
            'personne.1.nom': ['Willison'],
            'personne.2.prenom': ['Adrian'],
            'personne.2.nom': ['Holovaty']})
    >>> d
    {'personne': {'1': {'nom': ['Willison'], 'prenom': ['Simon']},
    '2': {'nom': ['Holovaty'], 'prenom': ['Adrian']}}}
    >>> d['personne']
    {'1': {'prenom': ['Simon'], 'nom': ['Willison'],
    '2': {'prenom': ['Adrian'], 'nom': ['Holovaty']}
    >>> d['personne']['1']
    {'prenom': ['Simon'], 'nom': ['Willison']}

    # Attention : le résultat est imprévisible si le nombre de points dans une clé donnée est impair
    >>> DotExpandedDict({'c.1': 2, 'c.2': 3, 'c': 1})
    >>> {'c': 1}
    """
    def __init__(self, key_to_list_mapping):
        for k, v in key_to_list_mapping.items():
            current = self
            bits = k.split('.')
            for bit in bits[:-1]:
                current = current.setdefault(bit, {})
            # Et maintenant on affecte la valeur à la position actuelle
            try:
                current[bits[-1]] = v
            except TypeError: # Cas particulier : current n'est pas un dictionnaire
                current = {bits[-1]: v}
